package com.larry.tinyweb.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created by larry on 16/12/12.
 *
 * @author larry
 *
 * 注解的容器，使用单例
 */
public class AnnotationContainer {
    private Map<String, List<Unit>> annotationTypeContainer;
    private Map<Class, Unit> annotationContainer;
    private static AnnotationContainer container;
    private Object start;

    private AnnotationContainer() {
        this.annotationContainer = new HashMap<>();
        this.annotationTypeContainer = new HashMap<>();

    }

    public static AnnotationContainer getContainer() {
        if (container == null) {
            container = new AnnotationContainer();
            new AnnotationLoader(container).scannerAnnotation();
        }
        return container;
    }



    /**
     * 依赖注入
     * @param obj
     * @return
     * @throws NoSuchMethodException
     */
    public Object IOC(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class cls = obj.getClass();
        Field[] fields = cls.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Class<?> fieldType = field.getType();
                String fieldName = field.getName();

                //setter方法名
                String setterMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                //获取setter方法
                Method method = cls.getMethod(setterMethodName, fieldType);
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Object wired = getFromContainer(fieldType);
                    if (wired != null) {
                        method.invoke(obj, wired);
                    } else {
                        throw new RuntimeException("not find bean of " + fieldType.getSimpleName() + " in container");
                    }
                } else {
                    throw new RuntimeException("the method " + setterMethodName + " wrong parameter number");
                }
            }
        }
        return obj;
    }


    /**
     * 根据class获取类型为class获取其子类的unit
     * 父类的引用可以获取起子类的注解
     * @param cls
     * @return
     */
    private Unit queryUnit(Class cls) {
        Iterator<Map.Entry<Class, Unit>> iterator = annotationContainer.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Class, Unit> next = iterator.next();
            if (cls.isAssignableFrom(next.getKey())) {
                return next.getValue();
            }
        }
        return null;
    }


    /**
     * 从容器内获取已存在的注解对象
     * 如果对象的scope不是singleton则创建一个新的并返回
     * @param cls
     * @return
     */
    public Object getFromContainer(Class cls) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Unit unit = queryUnit(cls);
        if (unit == null) return null;
        else return getFromContainer(unit);
    }

    public Object getFromContainer(Unit unit) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class cls = unit.getClasz();
        if (unit.isSingleton()) {
            if (unit.getObj() == null) unit.setObj(IOC(cls.newInstance()));
            return unit.getObj();
        }
        return IOC(cls.newInstance());
    }


    /**
     * 注册注解到容器
     * @param cls
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public void put(Class cls) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Annotation[] annotations = cls.getAnnotations();
        if (annotations.length > 0) {
            // 判断是否是项目启动注解
            if (cls.isAnnotationPresent(Start.class)) {
                this.start = cls.newInstance();
            }

            Annotation scope = cls.getAnnotation(Scope.class);
            Unit annotationUnit;
            if (scope == null)
                annotationUnit = new Unit(cls);
            else
                annotationUnit = new Unit(cls, ((Scope)scope).value());

            annotationContainer.put(cls, annotationUnit);

            for (Annotation an : annotations) {
                Class<? extends Annotation> annotationType = an.annotationType();
                List<Unit> units = annotationTypeContainer.get(annotationType.getName());
                if (units == null) {
                    units = new ArrayList<>();
                    annotationTypeContainer.put(annotationType.getName(), units);
                }
                units.add(annotationUnit);
            }
        }
    }

    public static Object getStart() {
        return getContainer().start;
    }


    /**
     * 获取被某个注解修饰的所有Unit
     * @param annotation
     * @return
     */
    public List<Unit> typeOfAnnotation(Class annotation) {
        return annotationTypeContainer.get(annotation.getName());
    }
}
